package org.mobilesynergies.android.epic.service;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;


import org.mobilesynergies.android.epic.service.Manifest.permission;
import org.mobilesynergies.epic.client.EpicClient;
import org.mobilesynergies.epic.client.EpicClientException;
import org.mobilesynergies.epic.client.EpicCommandInfo;


import org.mobilesynergies.android.epic.service.core.AdministrationInterface;
import org.mobilesynergies.android.epic.service.core.ApplicationSession;
import org.mobilesynergies.android.epic.service.core.ApplicationInterface;

import org.mobilesynergies.android.epic.service.core.states.ServiceConnectedState;
import org.mobilesynergies.android.epic.service.core.states.ServiceInitializingState;
import org.mobilesynergies.android.epic.service.interfaces.IApplicationStatusChangeCallback;
import org.mobilesynergies.android.epic.service.interfaces.IEpicServiceAdministrationInterface;
import org.mobilesynergies.android.epic.service.interfaces.IEpicServiceApplicationInterface;
import org.mobilesynergies.android.epic.service.interfaces.IIncomingMessageCallback;
import org.mobilesynergies.android.epic.service.interfaces.IServiceStatusChangeCallback;

import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;


/**
 * The service running as a background process on Android. 
 * It offers two interfaces: 
 * <ul>
 * <li>An interface for 3rd party applications to bind to (see {@link EpicServiceApplicationInterface}). 
 * <li>An interface for activities that configure the service or show the status of the service (see {@link EpicServiceAdministrationInterface}).
 * </ul>
 * The EpicService class is responsible to establish a connection to the epic network when possible. It handles changes in network (i.e., internet) availability and manages the EPIC client.
 * 
 * @author Peter Rautek
 *
 */

public class EpicService extends Service {
	private static String CLASS_TAG = EpicService.class.getSimpleName();
	
	
	/**
	 * 
	 * @author Peter
	 * Thread that will try to connect to the xmpp server 
	 */
	private class ConnectionThread extends Thread {
		private boolean mIsFinished = false;
		private boolean mSucceded = false;
		private boolean mStopped = false;
		private int mRemainingTries = 3;
		private String mServer = null;
		
		public ConnectionThread(String server){
			mServer = server;
		}
	
		public ConnectionThread(String server, int iNumberOfTries) {
			mServer = server;
			if(iNumberOfTries>0){
				mRemainingTries=iNumberOfTries;
			}
		}
		
		public void run(){
			
			while((!mSucceded)&&(!mStopped)) {
				try {
					mSucceded = mEpicClient.establishConnection(mServer);
				} catch (EpicClientException e) {
					e.printStackTrace();
				}
				
				if(!mSucceded) {

					Log.d(CLASS_TAG, "Didn't succeed to connect to XMPP server.");
					mRemainingTries--;
					if(mRemainingTries==0)
					{
						Log.d(CLASS_TAG, "Giving up to connect to XMPP server.");
						mStopped = true;
					} else {
						//we wait for some time and try again
						try {
							Log.d(CLASS_TAG, "Will try again to connect to XMPP server in 5 seconds.");
							sleep(5000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
			
			if((mSucceded)&&(mHandleConnectedToXmppServer!=null)){
				//send a message to the main thread of the service
				mHandleConnectedToXmppServer.sendEmptyMessage(0);
			}

			//the thread is finished
			mIsFinished = true;
		}
		
		public boolean isFinished() {
			return mIsFinished;
		}
		
		public void stopThread(){
			mStopped=true;
		}
		
	};
	
	/** 
	 * Thread that is run whenever the service needs to connect to the xmpp server
	 */
	private ConnectionThread mConnectionThread = null; 


	/**
	 * Stores the callbacks that wish to be informed about changes in service availability.
	 */
	private HashSet<IServiceStatusChangeCallback> mSetServiceStatusCallbacks = new HashSet<IServiceStatusChangeCallback>();
	
	/**
	 * Stores the currently authenticated {@link ApplicationSession}s with a unique string that is also returned to the application to identify itself
	 */
	private HashMap<String, ApplicationSession> mApplicationSessions = new HashMap<String, ApplicationSession>();
	
	/**
	 * The implementation of the application interface
	 */
	private final IEpicServiceApplicationInterface mApplicationInterface = (IEpicServiceApplicationInterface) new ApplicationInterface(this);

	/**
	 * The implementation of the administration interface
	 */
	private final IEpicServiceAdministrationInterface mAdministrationInterface = new AdministrationInterface(this); 

	//TODO make the server name configureable 
	public static final String XMPP_SERVER = "blindeshendl.cg.tuwien.ac.at";

	/**
	 * Identifying the version number of the epic standard that is implemented with this service
	 */
	public static final int EPIC_SERVICE_VERSION_NUMBER = 1;

	/**
	 * A state object that defers the implementation of all state dependent methods of the service.   
	 */
	private EpicServiceState mServiceStateObject = new ServiceInitializingState();

	/**
	 * A XmppClient handling the interaction with the XmppServer.
	 */
	public EpicClient mEpicClient = new EpicClient();  

	/**
	 * Called when the service is created
	 */
	
	public void onCreate() {
		super.onCreate();
		
		
		//TODO check if we have internet connection
		//if yes: call connect()
		connect();
		//if no: do nothing -> the broadcastreceiver will inform us and call connect()
		
	}
	
	
	private void connect(){
		if(mEpicClient.isConnected()){
			return;
		}
		if(mConnectionThread==null){
			mConnectionThread = new ConnectionThread(XMPP_SERVER);
		}
		
		if(mConnectionThread.isFinished()){
			mConnectionThread = new ConnectionThread(XMPP_SERVER);
		}
		
		if(!mConnectionThread.isAlive()){
			mConnectionThread.start();
		}
	}
	
	private Handler mHandleConnectedToXmppServer = new Handler(){
		
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			//after we are connected to the xmpp server
			//we try to authenticate with the id of this epic device
			//TODO if this device doesn't have an id we need to inform the user to generate one
			try {
				//TODO make configurable
				if(mEpicClient.authenticateUser("peter", "peter"))
				{
					changeState(ServiceConnectedState.SERVICE_STATE);
				}
			} catch (EpicClientException e) {
				e.printStackTrace();
			}
		}
	};

	/**
	 * Overriding the android.os.Service function onBind. 
	 * 
	 * @return Returns the desired interface to the calling activity 
	 */
	
	public IBinder onBind(Intent intent) {
		// Select the interface to return.  
		if (IEpicServiceApplicationInterface.class.getName().equals(intent.getAction())) {
			return (IBinder) mApplicationInterface;
		}
		if (IEpicServiceAdministrationInterface.class.getName().equals(intent.getAction())) {
			return (IBinder) mAdministrationInterface;
		}

		return null;
	}

	/**
	 * Is called internally if the state of the service changes
	 * 
	 * @param serviceState The new state of the service
	 */
	private void changeState(String serviceState) {
		String oldState = mServiceStateObject.getState();
		if(oldState.equalsIgnoreCase(serviceState)){
			//we don't need to change the state
			return;
		}
		
		if(serviceState.equalsIgnoreCase(ServiceConnectedState.SERVICE_STATE)){
			mServiceStateObject = new ServiceConnectedState();
		} 
		//TODO else if ...
		
		
		String status = mServiceStateObject.getState();
		Iterator<IServiceStatusChangeCallback> iter = mSetServiceStatusCallbacks.iterator();
		while(iter.hasNext()){
			IServiceStatusChangeCallback s = iter.next();
			try {
				s.onServiceStatusChanged(status);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}


	/** 
	 * The application can register a callback to be informed about changes of service availability.
	 * The callback is called either when the connection to the xmpp server was established and the user authentication suceeded or when the connection to the xmpp server was lost or the user logged out.
	 * @param callback The callback that will be notified about changes
	 */
	public void registerServiceStatusChangeCallback(IServiceStatusChangeCallback callback) {
		mSetServiceStatusCallbacks.add(callback);
	}

	/**
	 * Is called by the ApplicationInterfaceImplementation to add an ApplicationSession. The application is verified to have the rights to connect to the epic service.
	 * @param applicationKey The key that will be used by the application to identify itself.
	 * @param appSession The session object holding the details of this application session.
	 */
	public void addSession(String applicationKey, ApplicationSession appSession) {
		mApplicationSessions.put(applicationKey, appSession);		
	}

	/**
	 * This function is called to get a ApplicationSession object. 
	 * @param sessionKey
	 * @return The ApplicationSession object identified by the sessionKey or null if the sessionKey is unknown.
	 */
	public ApplicationSession getApplicationSessions(String sessionKey) {
		return mApplicationSessions.get(sessionKey);
	}


	/**
	 * State dependent method.
	 * Implementation is deferred to a state object.  
	 * @param sessionKey
	 * @param callback
	 */
	public void registerApplicationStatusChangeCallback(String sessionKey,
			IApplicationStatusChangeCallback callback) {
		mServiceStateObject.registerApplicationStatusChangeCallback(sessionKey, callback);
	}

	
	/**
	 * 
	 * State dependent method.
	 * Implementation is deferred to a state object.  
	 * @param sessionToken
	 * @param messageCallback
	 */
	public void registerSubscriberCallback(String sessionToken,
			IIncomingMessageCallback messageCallback) {
		int iPermissionReceiveMessagesStatus = checkCallingPermission(permission.receivemessages);
		if(iPermissionReceiveMessagesStatus==PackageManager.PERMISSION_DENIED){
			Log.w(CLASS_TAG, "The calling application is missing the permission"+ permission.receivemessages+".");
			return;
		}

		mServiceStateObject.registerSubscriberCallback(sessionToken, messageCallback);

	}

	/**
	 * State dependent method.
	 * Implementation is deferred to a state object.  
	 * @param sessionToken
	 * @param object
	 * @return
	 */
	public Bundle requestResponse(String sessionToken, Bundle object) {
		int iPermissionReceiveMessagesStatus = checkCallingPermission(permission.receivemessages);
		if(iPermissionReceiveMessagesStatus==PackageManager.PERMISSION_DENIED){
			Log.w(CLASS_TAG, "The calling application is missing the permission"+ permission.receivemessages+".");
		}
		int iPermissionSendMessagesStatus = checkCallingPermission(permission.sendmessages);
		if(iPermissionSendMessagesStatus==PackageManager.PERMISSION_DENIED){
			Log.w(CLASS_TAG, "The calling application is missing the permission"+ permission.sendmessages+".");
		}

		if((iPermissionReceiveMessagesStatus==PackageManager.PERMISSION_DENIED)||(iPermissionSendMessagesStatus==PackageManager.PERMISSION_DENIED)){
			return null;
		}

		return mServiceStateObject.requestResponse(sessionToken, object); 
	}

	/**
	 * 
	 * State dependent method.
	 * Implementation is deferred to a state object.  
	 * @param sessionToken
	 * @param object
	 */
	public void sendMessage(String sessionToken, Bundle object) {
		int iPermissionSendMessagesStatus = checkCallingPermission(permission.sendmessages);
		if(iPermissionSendMessagesStatus==PackageManager.PERMISSION_DENIED){
			Log.w(CLASS_TAG, "The calling application is missing the permission"+ permission.sendmessages+".");
			return;
		}
		mServiceStateObject.sendMessage(sessionToken, object);
	}

	/**
	 * 
	 * State dependent method.
	 * Implementation is deferred to a state object.  
	 * @param sessionToken
	 */
	public void removeSubscriberCallback(String sessionToken) {
		mServiceStateObject.removeSubscriberCallback(sessionToken);
	}


	public EpicClient getClient() {
		return mEpicClient;
	}



}